package society.framework.core.communication;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Set;

import society.framework.core.BaseAutomaton;
import society.framework.core.Callback;
import society.framework.core.SocietyInformation;

/**
 * The class that is responsible with registering and sending messages to automatons. It implements singleton pattern.
 * @author Micu Andrei
 *
 */
public class MessageRouter implements Serializable{
	private static final long serialVersionUID = 3480724758128950269L;
	private static MessageRouter instance;

	protected HashMap<String, BaseAutomaton> subscribers;
	
	protected NetworkMessagingAutomaton networkMessagingAutomaton;
	
	private MessageRouter(){
		subscribers = new HashMap<String, BaseAutomaton>();
		
	}
	
	public void startNetworkMessaging(){
		networkMessagingAutomaton = new NetworkMessagingAutomaton(this);
		networkMessagingAutomaton.start();
		MessageRouter.getInstance().sendLocal(SocietyInformation.UI_MANAGER_NAME,
                new Message("MessageRouter", "green:Network messaging started."));
	}
	
	public void stopNetworkMessaging(){
		this.networkMessagingAutomaton.stopSafely(new Callback() {
					
			public void handleMethod() {
				MessageRouter.getInstance().sendLocal(SocietyInformation.UI_MANAGER_NAME,
		                new Message("MessageRouter", "green:Network messaging successfully stopped."));
			}
		});
	}
	
	public static MessageRouter getInstance(){
		if(instance==null){
			instance = new MessageRouter();
		}
		return instance;
	}
	
	/**
	 * Gets a list containing the names of the automatons which were subscribed to the MessageRouter.
	 * @return - list containing the automaton names
	 */
	public Set<String> getSubscriberNames(){
		return subscribers.keySet();
	}
	
	/**
	 * Adds the automaton to the list of possible recipients. It is identified by name parameter
	 * @param name - the name which this automaton identifies with
	 * @param automaton - the automaton to subscribe
	 * @return - true if operation succeeded
	 */
	public boolean subscribe(String name, BaseAutomaton automaton){
		return subscribers.put(name, automaton)!=null;
	}
	
	/**
	 * Removes the automaton with the name given as parameter from the subscribers list.
	 * @param name - the name which this automaton identifies with
	 * @return - true if operation succeeded
	 */
	public boolean unsubscribe(String name){
		return subscribers.remove(name)!=null;
	}
	
	/**
	 * Removes all subscribers from the router
	 */
	public void clearSubscribers(){
		subscribers.clear();
	}
	
	/**
	 * Method that sends a message to an automaton specified by the name on the local application.
	 * If there is no automaton on the local machine registered with that name, it is sent on the remote application.
	 * @param name - name with which the automaton registered
	 * @param message - the message
	 * @return - true if message was sent successfully, false if it was sent on the remote application
	 */
	public boolean send(final String name, final Message message){
		if(!sendLocal(name, message)){
			sendRemote(name, message);
			return false;
		}
		return true;
	}
	
	/**
	 * Method that sends a message to all the automatons specified by the names on the local application.
	 * If there is no automaton on the local machine registered with a specific name, it is sent on the remote application.
	 * @param names - array containing the names with which the automatons registered
	 * @param message - the message
	 * @return - true if all messages were sent successfully, false if at least one was sent on the remote application
	 */
	public boolean send(final String names[], final Message message){
		boolean success=true;
		for (int i=0; i<names.length; i++) {
			if (!sendLocal(names[i], message)) {
				success = false;
				sendRemote(names[i], message);
			}
		}
		return success;
	}
	
	/**
	 * Method that sends a message to an automaton specified by the name on the local application. 
	 * @param name - name with which the automaton registered
	 * @param message - the message
	 * @return - true if message was sent successfully
	 */
	public boolean sendLocal(final String name, final Message message){
		BaseAutomaton automaton=subscribers.get(name);
		if(automaton!=null){
			automaton.addMessage(message);
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Method that sends a message to all the automatons specified by the names on the local application.
	 * @param names - array containing the names with which the automatons registered
	 * @param message - the message
	 * @return - true if all messages were sent successfully
	 */
	public boolean sendLocal(final String names[], final Message message){
		boolean success=true;
		for (int i=0; i<names.length; i++) {
			if (!sendLocal(names[i], message)) {
				success = false;
			}
		}
		return success;
	}
	
	/**
	 * Method that uses the NetworkMessagingAutomaton to send the message to the remote application.
	 * @param name - name with which the automaton registered
	 * @param message - the message
	 */
	public void sendRemote(final String name, final Message message){
		String[] toData = new String[2];
		toData[0] = name;
		toData[1] = message.data.toString();
		Message messageToSend = new Message(message.from, toData);
		networkMessagingAutomaton.addMessage(messageToSend);
	}
}
